package log

import (
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"sync/atomic"
)

var _ = Target(&TargetFile{})

//TargetFile implement Target with normal file printing.
type TargetFile struct {
	dir      string
	filename string

	levels []int
	using  int32

	file *os.File

	fileLock sync.Mutex
}

//MakeTargetFile make a TargetFile instance with specific directory and file name.
func MakeTargetFile(dir string, fn string) (*TargetFile, error) {
	t := &TargetFile{
		dir:      dir,
		filename: fn,
		levels:   append([]int{}, allLevels...),
		using:    0,
	}

	f, err := t.openFile()
	if nil != err {
		return nil, err
	}
	t.file = f

	return t, nil
}

//Name implement for Target.Name()
func (t *TargetFile) Name() string {
	return "file." + t.filename
}

//IncrUsing implement for Target.IncrUsing()
func (t *TargetFile) IncrUsing() {
	atomic.AddInt32(&t.using, 1)
}

//IsIgnore implement for Target.IsIgnore()
func (t *TargetFile) IsIgnore(l int) bool {
	for _, s := range t.levels {
		if s == l {
			return false
		}
	}
	return true
}

//SetLevels set levels this target should output.
func (t *TargetFile) SetLevels(ls []int) {
	t.levels = append([]int{}, ls...)
}

//Put implement for Target.Put()
func (t *TargetFile) Put(msg string) {
	fmt.Fprintln(t.file, msg)
}

//Close close file
func (t *TargetFile) Close() {
	atomic.AddInt32(&t.using, -1)
	t.fileLock.Lock()
	defer t.fileLock.Unlock()
	if atomic.LoadInt32(&t.using) != 0 {
		return
	}

	t.file.Sync()
	t.file.Close()
}

//ReopenFile open file again. used for tools like logrotate.
func (t *TargetFile) ReopenFile() error {
	f, err := t.openFile()
	if nil != err {
		return err
	}

	t.fileLock.Lock()
	defer t.fileLock.Unlock()
	t.file = f

	return nil
}

func (t *TargetFile) openFile() (*os.File, error) {
	path := filepath.Join(t.dir, t.filename)
	f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
	if nil != err {
		return nil, fmt.Errorf("can't open or create log file [%s]", path)
	}
	return f, nil
}
